package com.intersult.nutils.net.filter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Dieter Kaeppel
 * @date   10.11.2011
 */
public class GzipFilter implements Filter {
	private static final String SERVLET_INCLUDE = "javax.servlet.include.request_uri";
	private static final String MIME_ACCEPT_ENCODING = "Accept-Encoding";
	private static final String MIME_CONTENT_ENCODING = "Content-Encoding";
	private static final String ENCODING_GZIP = "gzip";
	private static final String JSF_PROJECT_STAGE = "javax.faces.PROJECT_STAGE";
	private static final String JSF_PROJECT_STAGE_PRODUCTION = "Production";
	public static final Pattern ENCODING_SPLITTER = Pattern.compile(",\\s*");
	
	private boolean compress;
	
	public boolean isCompress() {
		return compress;
	}
	public void setCompress(boolean compress) {
		this.compress = compress;
	}
	
	@Override
	public void init(FilterConfig filterConfig) {
		String stage = filterConfig.getServletContext().getInitParameter(JSF_PROJECT_STAGE);
		compress = JSF_PROJECT_STAGE_PRODUCTION.equalsIgnoreCase(stage);
	}
	@Override
	public void destroy() {
	}
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
			ServletException {
		HttpServletRequest httpRequest = (HttpServletRequest)request;
		HttpServletResponse httpResponse = (HttpServletResponse)response;
		
		if (compress && request.getAttribute(SERVLET_INCLUDE) == null && acceptsEncoding(httpRequest, ENCODING_GZIP)) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ResponseWrapper wrapper = new ResponseWrapper(httpResponse, outputStream);
            
            chain.doFilter(request, wrapper);
            wrapper.flushBuffer();
            
            if (wrapper.getStatus() == HttpServletResponse.SC_NO_CONTENT)
            	return;
            
            byte[] uncompressed = outputStream.toByteArray();
            if (uncompressed.length == 0)
            	return;
            
            if (uncompressed.length < 512) {
            	response.getOutputStream().write(uncompressed);
                response.setContentLength(uncompressed.length);
            	return;
            }
            
            byte[] compressed = compress(uncompressed);
            if (compressed.length >= uncompressed.length) {
            	response.getOutputStream().write(uncompressed);
                response.setContentLength(uncompressed.length);
            	return;
            }
            
//            double gain = (double)(uncompressed.length - compressed.length) / uncompressed.length;
//            System.out.println(MessageFormat.format("GZIP {0} {1} -> {2} ({3,number,#,#0.0%})",
//            	httpRequest.getRequestURI(), uncompressed.length, compressed.length, gain));
            
            httpResponse.addHeader(MIME_CONTENT_ENCODING, ENCODING_GZIP);
            response.setContentLength(compressed.length);
            response.getOutputStream().write(compressed);
        } else {
            chain.doFilter(request, response);
        }
    }
	
	private static byte[] compress(byte[] uncompressed) throws IOException {
		ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
		GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteStream);
		gzipOutputStream.write(uncompressed);
		gzipOutputStream.close();
		byte[] compressed = byteStream.toByteArray();
		return compressed;
	}

    public static boolean acceptsEncoding(HttpServletRequest httpRequest, String encoding) {
    	Enumeration<?> headers = httpRequest.getHeaders(MIME_ACCEPT_ENCODING);
    	if (headers == null)
    		return false;
    	while (headers.hasMoreElements()) {
    		String header = (String)headers.nextElement();
    		String[] headerEncodings = ENCODING_SPLITTER.split(header);
    		for (String headerEncoding : headerEncodings) {
    			if (encoding.equalsIgnoreCase(headerEncoding))
    				return true;
    		}
    	}
    	return false;
	}
    
	public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
		byte[] buffer = new byte[2048];
		for (;;) {
			int size = inputStream.read(buffer);
			if (size < 0)
				break;
			outputStream.write(buffer, 0, size);
		}
		inputStream.close();
	}
}